package com.witframework.web.tag;

import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspTagException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.tagext.BodyContent;
import javax.servlet.jsp.tagext.BodyTagSupport;

/**
 * JSP Tag <b>format</b>, used to format a Date for display.
 * <p>
 * The Date as a long in milliseconds is obtained from the body of the tag.
 * <p>
 * Uses the optional attribute <b>pattern</b> as the time pattern
 * string to use when formatting the Date.
 * <p>
 * The optional attribute <b>timeZone</b> can be set to the id of
 * a timeZone script varaible so that the Date if adjusted for
 * that timeZone.
 * <p>
 * If the optional attribute <b>locale</b> is true, the Date
 * is formatted for the clients locale if known.
 * <p>
 * The optional attribute <b>date</b> can be set to a
 * Date object using a runtime expression value.  If set,
 * date will be used instead of the tag body.
 * <p>
 * The optional attribute <b>default</b> can be set to a
 * default string to output if the date object doesn't exist
 * or the tag body is not a valid date.  If no default is set,
 * the string "Invalid Date" is output.
 * <p>
 * The optional attribute <b>localeRef</b> can be used to specify
 * the name of a page, session, application, or request scope attribute
 * of type java.util.Locale to use.
 * <p>
 * JSP Tag Lib Descriptor
 * <p><pre>
 * &lt;name&gt;format&lt;/name&gt;
 * &lt;tagclass&gt;org.apache.taglibs.datetime.FormatTag&lt;/tagclass&gt;
 * &lt;bodycontent&gt;JSP&lt;/bodycontent&gt;
 * &lt;info&gt;Formats a date for output.&lt;/info&gt;
 *   &lt;attribute&gt;
 *     &lt;name&gt;pattern&lt;/name&gt;
 *     &lt;required&gt;false&lt;/required&gt;
 *     &lt;rtexprvalue&gt;false&lt;/rtexprvalue&gt;
 *   &lt;/attribute&gt;
 *   &lt;attribute&gt;
 *     &lt;name&gt;patternId&lt;/name&gt;
 *     &lt;required&gt;false&lt;/required&gt;
 *     &lt;rtexprvalue&gt;false&lt;/rtexprvalue&gt;
 *   &lt;/attribute&gt;
 *   &lt;attribute&gt;
 *     &lt;name&gt;timeZone&lt;/name&gt;
 *     &lt;required&gt;false&lt;/required&gt;
 *     &lt;rtexprvalue&gt;false&lt;/rtexprvalue&gt;
 *   &lt;/attribute&gt;
 *   &lt;attribute&gt;
 *     &lt;name&gt;locale&lt;/name&gt;
 *     &lt;required&gt;false&lt;/required&gt;
 *     &lt;rtexprvalue&gt;false&lt;/rtexprvalue&gt;
 *   &lt;/attribute&gt;
 *   &lt;attribute&gt;
 *     &lt;name&gt;date&lt;/name&gt;
 *     &lt;required&gt;false&lt;/required&gt;
 *     &lt;rtexprvalue&gt;true&lt;/rtexprvalue&gt;
 *   &lt;/attribute&gt;
 *   &lt;attribute&gt;
 *     &lt;name&gt;default&lt;/name&gt;
 *     &lt;required&gt;false&lt;/required&gt;
 *     &lt;rtexprvalue&gt;false&lt;/rtexprvalue&gt;
 *   &lt;/attribute&gt;
 *   &lt;attribute&gt;
 *     &lt;name&gt;localeRef&lt;/name&gt;
 *     &lt;required&gt;false&lt;/required&gt;
 *     &lt;rtexprvalue&gt;false&lt;/rtexprvalue&gt;
 *   &lt;/attribute&gt;
 *   &lt;attribute&gt;
 *     &lt;name&gt;symbolsRef&lt;/name&gt;
 *     &lt;required&gt;false&lt;/required&gt;
 *     &lt;rtexprvalue&gt;false&lt;/rtexprvalue&gt;
 *   &lt;/attribute&gt;
 * </pre>
 *
 * @author Glenn Nielsen
 * @author Mark Femal
 */
public class DateFormatTag extends BodyTagSupport
{

    // format tag attributes

    // Optional attribute, use users locale if known when formatting date
    private boolean locale_flag = false;
    // Optional attribute, time pattern string to use when formatting date
    private String pattern = null;
    // Optional attribute, name of script variable to use as pattern
    private String patternid = null;
    // Optional attribute, timeZone script variable id to use when formatting date
    private String timeZone_string;
    // Optional attribute, date object from rtexprvalue
    private Date date = null;
    // Optional attribute, the default text if the tag body or date given is invalid/null
    private String default_text = "";
    // Optional attribute, the name of an attribute which contains the Locale
    private String localeRef = null;
    // Optional attribute, name of script variable to use as date symbols source
    private String symbolsRef = null;

    // format tag invocation variables

    // The symbols object
    private DateFormatSymbols symbols = null;
    // The date to be formatted an output by tag
    private Date output_date = null;

    /**
     * Method called at start of tag, always returns EVAL_BODY_TAG
     *
     * @return EVAL_BODY_TAG
     */
    public final int doStartTag() throws JspException
    {
        output_date = date;
        return EVAL_BODY_TAG;
    }

    /**
     * Method called at end of format tag body.
     *
     * @return SKIP_BODY
     */
    public final int doAfterBody() throws JspException
    {
        // Use the body of the tag as input for the date
        BodyContent body = getBodyContent();
        String s = body.getString().trim();
        // Clear the body since we will output only the formatted date
        body.clearBody();
        if( output_date == null ) {
            long time;
            try {
                time = Long.valueOf(s).longValue();
                output_date = new Date(time);
            } catch(NumberFormatException nfe) {
            }
        }

        return SKIP_BODY;
    }

    /**
     * Method called at end of Tag
     *
     * @return EVAL_PAGE
     */
    public final int doEndTag() throws JspException
    {
        String date_formatted = default_text;

        if (output_date != null) {
            // Get the pattern to use
            SimpleDateFormat sdf;
            String pat = pattern;

            if (pat == null && patternid != null) {
                Object attr = pageContext.findAttribute(patternid);
                if (attr != null)
                    pat = attr.toString();
            }

            if (pat == null) {
                sdf = new SimpleDateFormat();
                pat = sdf.toPattern();
            }

            // Get a DateFormatSymbols
            if (symbolsRef != null) {
                symbols = (DateFormatSymbols) pageContext.findAttribute(symbolsRef);
                if (symbols == null) {
                    throw new JspException(
                            "datetime format tag could not find dateFormatSymbols for symbolsRef \"" +
                            symbolsRef + "\".");
                }
            }

            // Get a SimpleDateFormat using locale if necessary
            if (localeRef != null) {
                Locale locale = (Locale) pageContext.findAttribute(localeRef);
                if (locale == null) {
                    throw new JspException(
                            "datetime format tag could not find locale for localeRef \"" +
                            localeRef + "\".");
                }

                sdf = new SimpleDateFormat(pat, locale);
            } else if (locale_flag) {
                sdf = new SimpleDateFormat(pat,
                        (Locale) pageContext.getRequest().getLocale());
            } else if (symbols != null) {
                sdf = new SimpleDateFormat(pat,
                        symbols);
            } else {
                sdf = new SimpleDateFormat(pat);
            }

            // See if there is a timeZone
            if (timeZone_string != null) {
                TimeZone timeZone =
                        (TimeZone) pageContext.getAttribute(timeZone_string,
                                PageContext.SESSION_SCOPE);
                if (timeZone == null) {
                    throw new JspTagException("Datetime format tag timeZone " +
                            "script variable \"" + timeZone_string +
                            " \" does not exist");
                }
                sdf.setTimeZone(timeZone);
            }

            // Format the date for display
            date_formatted = sdf.format(output_date);
        }

        try {
            pageContext.getOut().write(date_formatted);
        } catch (Exception e) {
            throw new JspException("IO Error: " + e.getMessage());
        }

        return EVAL_PAGE;
    }

    public void release()
    {
        super.release();
        locale_flag = false;
        pattern = null;
        patternid = null;
        date = null;
        localeRef = null;
        symbolsRef = null;
        symbols = null;
    }

    /**
     * Locale flag, if set to true, format date
     * for client's preferred locale if known.
     *
     * @param boolean use users locale, true or false
     */
    public final void setLocale(boolean flag)
    {
        locale_flag = flag;
    }

    /**
     * Set the time zone to use when formatting date.
     *
     * Value must be the name of a <b>timeZone</b> tag script
     * variable ID.
     *
     * @param String name of timeZone to use
     */
    public final void setTimeZone(String tz)
    {
        timeZone_string = tz;
    }

    /**
     * Set the pattern to use when formatting Date.
     *
     * @param String SimpleDateFormat style time pattern format string
     */
    public final void setPattern(String str)
    {
        pattern = str;
    }

    /**
     * Set the pattern to use when parsing Date using a script variable
     * attribute.
     *
     * @param String name of script variable attribute id
     */
    public final void setPatternId(String str)
    {
        patternid = str;
    }

    /**
     * Set the date to use (overrides tag body) for formatting
     *
     * @param DateStr to use for formatting (could be null)
     */
    public final void setDate(Date date)
    {
        this.date = date;
    }

    /**
     * Set the default text if an invalid date or no tag body is given
     *
     * @param String to use as default text
     */
    public final void setDefault(String default_text)
    {
        this.default_text = default_text;
    }

    /**
     * Provides a key to search the page context for in order to get the
     * java.util.Locale to use.
     *
     * @param String name of locale attribute to use
     */
    public void setLocaleRef(String value)
    {
        localeRef = value;
    }

    /**
     * Provides a key to search the page context for in order to get the
     * java.text.DateFormatSymbols to use
     *
     * @param symbolsRef
     */
    public void setSymbolsRef(String symbolsRef) throws JspException
    {
        this.symbolsRef = symbolsRef;
    }
}
